'''
 * CaRComp Execution Environment
 * 
 * Copyright (C) 2011 Taylor L. Riche
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
'''

from carcomp.framework import component, connector

class Abstraction(component.Component):
    '''
    classdocs
    '''

    def __init__(self, name):
        '''
        Constructor
        '''
        self._name = name
        self._components = {}
        self._connections = {}
        self._ports = {}
        
    def run_component(self, port, msg):
        '''Send the msg to the implementation connected to port'''
        raise component.UnimplementedAbstractionError
        
    def add_port(self, port_name, implementation, output=False):
        '''Add port to abstraction, if implementation exists create a connector'''
        handler = '__ABS__'
        new_port = None
        if implementation is not None:
            comp = implementation[0]
            port = implementation[1]
            src = None
            dst = None
            if not output:
                new_port = component.Port(port_name, handler)
                src = connector.Endpoint(self, new_port)
                dst = comp.get_endpoint(port)
            else:
                handler = '__OUT__'
                new_port = component.Port(port_name, handler)
                src = comp.get_endpoint(port)
                dst = connector.Endpoint(self, new_port)
            conn = connector.Connector(src, dst)
            self.add_connector(conn)
        else:
            new_port = component.Port(port_name, handler)
        self._ports[port_name] = new_port
       
    def add_component(self, comp):
        if not isinstance(comp, component.Component):
            raise component.NotAComponentError
        self._components[comp.get_name()] = comp
        
    def get_component(self, name):
        return self._components[name]
        
    def add_connector(self, conn):
        self._connections[conn.get_src_name()] = conn
        
    def create_connection(self, src_name, sport_name, dst_name, dport_name):
        src = None
        dst = None
        if src_name == self._name:
            src = connector.Endpoint(self, self._ports[sport_name])
        else:
            src = self._components[src_name].get_endpoint(sport_name)
        if dst_name == self._name:
            dst = connector.Endpoint(self, self._ports[dport_name])
        else:
            dst = self._components[dst_name].get_endpoint(dport_name)
        self.add_connector(connector.Connector(src, dst))
        
    def resolve_endpoint(self, port_name):
        '''Returns a connector attached to the given port.
        ''' 
        if port_name not in self._ports.keys():
            raise component.UnknownPortError
        key_string = '{}:{}'.format(self._name, port_name)
        if key_string not in self._connections.keys():
            exists = False
            for conn_tuple in self._connections.items():
                conn = conn_tuple[1]
                if conn.get_dest_name() == key_string:
                    key_string = conn.get_src_name()
                    exists = True
                    break
            if not exists:
                raise component.UnimplementedAbstractionError
        final_connect = self._connections[key_string]
        if isinstance(final_connect.get_dst_comp(), Abstraction) and final_connect.get_dst_comp() != self:
            final_connect = final_connect.get_dst_comp().resolve_endpoint(final_connect.get_dest_port_name())
        if isinstance(final_connect.get_src_comp(), Abstraction) and final_connect.get_src_comp() != self:
            final_connect = final_connect.get_src_comp().resolve_endpoint(final_connect.get_src_port_name())
        return final_connect
        
    def get_name(self):
        '''Returns the name of the abstraction'''
        return self._name

    def get_endpoint(self, port_name):
        '''Return the endpoint associated with the given port.'''
        if port_name not in self._ports.keys():
            raise component.UnknownPortError
        return connector.Endpoint(self, self._ports[port_name])
    
    def resolve(self):
        '''Return a list of internal components and "clean" connectors'''
        #print(self._name + ":")
        imp_list = [comp_tuple[1] for comp_tuple in self._components.items() if isinstance(comp_tuple[1], component.Implementation)]
        abs_list = [comp_tuple[1] for comp_tuple in self._components.items() if isinstance(comp_tuple[1], Abstraction)]
        has_abs = lambda x: isinstance(x.get_src_comp(), Abstraction) or isinstance(x.get_dst_comp(), Abstraction)
        conn_list = [conn_tuple[1] for conn_tuple in self._connections.items() if not has_abs(conn_tuple[1])]
        abs_conn_list = [conn_tuple[1] for conn_tuple in self._connections.items() if has_abs(conn_tuple[1])]
        '''print('({})imps:'.format(self._name))
        for imp in imp_list:
            print('\t' + imp.get_name())
        print('({})conns:'.format(self._name))
        for conn in conn_list:
            print('\t' + conn.get_name())
        print('({})absconns:'.format(self._name))
        for absconns in abs_conn_list:
            print('\t' + absconns.get_name())'''
        for abs in abs_list:
            inner_imp_list, inner_conn_list = abs.resolve()
            imp_list.extend(inner_imp_list)
            conn_list.extend(inner_conn_list)
        for conn in abs_conn_list:
            #print('\t' + conn.get_name())
            new_conn = None
            if isinstance(conn.get_dst_comp(), Abstraction) and conn.get_src_comp() != self and conn.get_dst_comp() != self:
                new_conn = connector.Connector(conn.get_src(), conn.get_dst_comp().resolve_endpoint(conn.get_dest_port_name()).get_dst())
            if isinstance(conn.get_src_comp(), Abstraction) and conn.get_src_comp() != self and conn.get_dst_comp() != self:
                new_conn = connector.Connector(conn.get_src_comp().resolve_endpoint(conn.get_src_port_name()).get_src(), conn.get_dst())
            if new_conn is not None:
                #print('({})\tadding conn: '.format(self._name) + new_conn.get_name())
                conn_list.append(new_conn)
        '''print('({})after imps:'.format(self._name))
        for imp in imp_list:
            print('\t' + imp.get_name())
        print('({})after conns:'.format(self._name))
        for conn in conn_list:
            print('\t' + conn.get_name())'''
        return (imp_list, conn_list)
        
